package dominio.neuronio.especializacao;

import java.text.DecimalFormat;
import java.util.List;

import util.Util;

import dominio.ativacao.FuncaoAtivacao;
import dominio.neuronio.NeuronioAbstrato;
import dominio.neuronio.Sinapse;
import dominio.neuronio.Treinavel;
import dominio.padrao.Padrao;

public class NeuronioPerceptron extends NeuronioAbstrato implements Treinavel {
	
	private double bias = Math.random();
	
	/**
	 */
	public NeuronioPerceptron(){
		super();
	}
	
	/**
	 * @param funcaoAtivacao
	 */
	public NeuronioPerceptron( FuncaoAtivacao funcaoAtivacao ) {
		super( funcaoAtivacao );
	}	
	
	/**
	 * @param funcaoAtivacao
	 * @param bias
	 */
	public NeuronioPerceptron( FuncaoAtivacao funcaoAtivacao, double bias ) {
		super( funcaoAtivacao );
		this.bias = bias;
	}	

	/* 
	 * (non-Javadoc)
	 * @see dominio.neuronio.Neuronio#estimular(double[])
	 */
	public double estimular(Padrao padrao) {
		
		double[] estimulos = padrao.getEstimulos();
		for( int i = 0; i < this.sinapses.size(); i++ ) {
			this.sinapses.get( i ).setEntrada( estimulos[i] );
		}

		// processa a soma das saídas das sinapses
		this.entradaLiquida = this.totalizarEntradaLiquida();
		
		// aplica a função de ativação
		this.saida = this.funcaoAtivacao.aplicarFuncaoAtivacao( this.entradaLiquida + this.bias );

		// retorna y
		return this.saida;
	}
	
	/* (non-Javadoc)
	 * @see dominio.neuronio.Treinavel#treinar(double[][], double[])
	 */
	public void treinar(List<Padrao> padroesEntrada, double[] saidaDesejada ) {
		
		//
		Padrao padrao = padroesEntrada.get(0);
		this.criarSinapses( padrao );
		
		//quantidade de padroes
		int qtdePadroesEntrada = padroesEntrada.size();
		
		// quantidade de entradas por padrao */
        int estimulos = padrao.getQtdeEstimulos();
		
		/* vetor para calculo do erro de cada padrão de entrada */
		double[] erroPorPadrao = new double[qtdePadroesEntrada];

		double E = 1, txAprendizagem = 1;
		int epoca = 0, qtdeMaxIteracao = 1000;

		while(epoca < qtdeMaxIteracao && E > 0){

		    for(int i = 0; i < qtdePadroesEntrada ; i++){
		    	
		    	padrao = padroesEntrada.get(i);

				/* cálculo o erro para o padrão apresentado */
				erroPorPadrao[i] = saidaDesejada[i] - this.estimular( padrao ); 
	
				/* atualiza os pesos */
				for(int j = 0; j < estimulos; j++){
					Sinapse sinapse = this.sinapses.get( j );
					sinapse.ajustarPeso( txAprendizagem * erroPorPadrao[i] * sinapse.getEntrada() );
				}
				
				/* atualiza o bias */
				this.bias = this.bias + txAprendizagem * erroPorPadrao[i];
		    }

		    E = Util.erroQuadratico(erroPorPadrao);
		    epoca++;
		}
		
	}
	
	/**
	 * 
	 * @param bias
	 */
	public void setBias(double bias) {
		this.bias = bias;
	}
	
	/**
	 * 
	 * @return
	 */
	public double getBias() {
		return bias;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		DecimalFormat formatter = new DecimalFormat( "0.0000" );
		formatter.setPositivePrefix( " " );

		StringBuffer buf = new StringBuffer();
		buf.append( "\t# Neuronio Perceptron Simples\n" );

		// sinapses
		buf.append( "\t\tSinapses: \n" );
		if ( this.sinapses != null && this.sinapses.size() > 0 ) {
			for( Sinapse sinapse : this.sinapses ) {
				buf.append( "\t\t\t" ).append( sinapse.toString() ).append( "\n" );
			}
		}

		// entrada líquida
		buf.append( "\n\t\tEntrada liquida: " ).append( formatter.format( this.entradaLiquida ) ).append( "\n" );
		
		// bias 
		buf.append( "\t\tBias: " ).append( this.bias ).append( "\n" );

		// função de ativação
		buf.append( "\t\tFuncao de ativacao: " );
		buf.append( this.funcaoAtivacao != null ? this.funcaoAtivacao.toString() : null ).append( "\n" );
		
		// saida
		buf.append( "\t\tSaida: " ).append( formatter.format( this.saida ) ).append( "\n\n" );

		return buf.toString();
	}

}
